/*
 * RealCrypt.java
 *
 * Models a crypt that divides in empty space first, else, if no empty space, it bumps a neighbor
 *
 * @author Rumen Kostadinov
 */

package sim;
import java.util.ArrayList;
//import java.awt.Color;
import lib.KnuthRandom;
import lib.Probability;

public class DiversityCrypt extends Crypt{

    public void die(Scheduler s) throws Exception
    {
        // Change state/color to reflect death
	// Mark a dead crypt with setting the cloneId to -1.
	// Remove eCryptState dead, alive, mutant
        //SetCryptState(eCryptState.Dead);
	
        //setColor(Crypt.COLOR_DEAD);

        // A dead crypt should have clone_id=-1
        setCloneId(-1);

	// Remove the current crypt from division, death and mutation classes
    
	//s.removeCryptFromDivisionClass(getDivisionClassIndex(),getDivisionClass());
	//s.removeCryptFromDeathClass(getDeathClassIndex(),getDeathClass());
	//s.removeCryptFromNeutralMutationClass(getNeutralMutationClassIndex(),getNeutralMutationClass());
	//s.removeCryptFromSelectiveMutationClass(getSelectiveMutationClassIndex(),getSelectiveMutationClass());

	// Avoid function calls for speed, just access the variables directly
	s.changeClassOfCryptFromDivisionClass(this.divisionClassIndex, this.divisionClass, this.divisionRateClassDeadCryptsClass);
	s.changeClassOfCryptFromDeathClass(this.deathClassIndex, this.deathClass, this.deathRateClassDeadCryptsClass);
	s.changeClassOfCryptFromNeutralMutationClass(this.neutralMutationClassIndex, this.neutralMutationClass, this.neutralMutationRateClassDeadCryptsClass);
	s.changeClassOfCryptFromSelectiveMutationClass(this.selectiveMutationClassIndex, this.selectiveMutationClass, this.selectiveMutationRateClassDeadCryptsClass);

	//s.removeCryptFromMutationClass(getMutationClassNeutralIndex(),getMutationClassNeutral(),true);
	//s.removeCryptFromMutationClass(getMutationClassSelectiveIndex(),getMutationClassSelective(),false);
	
    }

    // Override division, if a cell chooses to divide, bump off a random neighbor
    public void divide(Scheduler s) throws Exception
    {
        // Dead neighbors
	byte deadCount = GetDeadCount();
	    
	// If there are dead neighboring cells, choose at random which empty spot to take at random
	if(deadCount>0){
	    int index = GetRandomDeadNeighborIndex(s, deadCount);	    	    
            
            Crypt daugh = _neighbors.get(index);

	    // Remove events from queue
	    //if( daugh._eventDeath != null ) s.RemoveEvent(daugh._eventDeath);
	    //if( daugh._eventMutate != null ) s.RemoveEvent(daugh._eventMutate);
	    //if( daugh._eventDivide != null ) s.RemoveEvent(daugh._eventDivide);

            //daugh.SetCryptState(this.GetCryptState());
            //daugh.setColor(this.getColor());
	    daugh.setCloneId(this._cloneId);

	    //double survival = s.getSurvival(this._cloneId);
	    //double reproduction = s.getReproduction(this._cloneId);
	    //double mutation = s.getMutation(this._cloneId);

	    //daugh.setSurvival(survival);
	    //daugh.setReproduction(reproduction);
	    //daugh.setMutation(mutation);

	    // From dead class to the dividing cell class
	    s.changeClassOfCryptFromDivisionClass(daugh.divisionClassIndex, daugh.divisionClass, this.divisionClass);
	    s.changeClassOfCryptFromDeathClass(daugh.deathClassIndex, daugh.deathClass, this.deathClass);
	    s.changeClassOfCryptFromNeutralMutationClass(daugh.neutralMutationClassIndex, daugh.neutralMutationClass, this.neutralMutationClass);
	    s.changeClassOfCryptFromSelectiveMutationClass(daugh.selectiveMutationClassIndex, daugh.selectiveMutationClass, this.selectiveMutationClass);

	    // Increase divisions
	    s.increaseDivisions();

	} else {

	    // Reproduce according to a probability
	    double randomNumber = s.GetRNG().randomDouble(); // [0..1)
	    if(randomNumber < _cryptDivisionProbability){

		// Reproduce in a random neighbor
		// This crypt always reproduces by choosing a random cell to occupy
		int index = GetRandomNeighborIndex(s);	    	    
		Crypt daugh = _neighbors.get(index);
		daugh.setCloneId(this._cloneId);
		
		//double survival = s.getSurvival(this._cloneId);
		//double reproduction = s.getReproduction(this._cloneId);
		//double mutation = s.getMutation(this._cloneId);
		
		//daugh.setSurvival(survival);
		//daugh.setReproduction(reproduction);
		//daugh.setMutation(mutation);
		
		// If the two cells belong to the same class, do not swap them within their class
		// otherwise, promote or demote a crypt to its new class
		if(daugh.divisionClass!=this.divisionClass){
		    s.changeClassOfCryptFromDivisionClass(daugh.divisionClassIndex, daugh.divisionClass, this.divisionClass);
		    //s.changeClassOfCryptFromDivisionClass(daugh.getDivisionClassIndex(),daugh.getDivisionClass(),this.getDivisionClass());
		}
		if(daugh.deathClass!=this.deathClass){
		    s.changeClassOfCryptFromDeathClass(daugh.deathClassIndex, daugh.deathClass, this.deathClass);
		    //s.changeClassOfCryptFromDeathClass(daugh.getDeathClassIndex(),daugh.getDeathClass(),this.getDeathClass());
		}
		if(daugh.neutralMutationClass!=this.neutralMutationClass){
		    s.changeClassOfCryptFromNeutralMutationClass(daugh.neutralMutationClassIndex, daugh.neutralMutationClass, this.neutralMutationClass);
		    //s.changeClassOfCryptFromMutationClass(daugh.getMutationClassNeutralIndex(),
		    //					  daugh.getMutationClassNeutral(),this.getMutationClassNeutral(), true);
		}
		if(daugh.selectiveMutationClass!=this.selectiveMutationClass){
		    s.changeClassOfCryptFromSelectiveMutationClass(daugh.selectiveMutationClassIndex, daugh.selectiveMutationClass, this.selectiveMutationClass);
		    //		    s.changeClassOfCryptFromMutationClass(daugh.getMutationClassSelectiveIndex(),
		    //				  daugh.getMutationClassSelective(),this.getMutationClassSelective(), false);
		}
		// Increase divisions
		s.increaseDivisions();
	    }
 	}
	

    }

    /** mutate - turns this crypt into a living mutant, even if the crypt was dead
     * @param s The scheduler of events
     */
    public void mutateNeutral(Scheduler s) throws Exception
    {

	// Choice of locus is pre-determined
	// to be neutral or selective
	int iRandLocus;
	// If microsat locus is mutated
	iRandLocus = s.GetRNG().randomInt(_neutralLociSize);
	
	//SetCryptState(eCryptState.AliveMutant); // note that a dead crypt will come to life
	
	// Increase the number of hits at this locus
	// Starting from 0 - no mutation, every time you hit the locus
	// with a mutation, increase by 1
	// Thus, locus can be post-processed into methylation, LOH, microsat, copy number, etc. states.
	// ms_shift_up is set to true always.
	// keep old microsat model, but always shift up.
	s.addNewClone(this._cloneId, iRandLocus, true);
	this._cloneId = s.getNewCloneId();
    }


    /** mutate - turns this crypt into a living mutant, even if the crypt was dead
     * @param s The scheduler of events
     */
    public void mutateSelective(Scheduler s) throws Exception
    {

	// Choice of locus is pre-determined
	// to be neutral or selective
	int iRandLocus;
	iRandLocus = _neutralLociSize + s.GetRNG().randomInt(_selectiveLociSize);

	// Getting an extra hit ensures that the current crypt will change rate class
	if(!s.isLocusMutated(iRandLocus,this._cloneId)){
	    //System.out.println("SUCCESS");
	    //SetCryptState(eCryptState.AliveMutant); // note that a dead crypt will come to life
	    
	    
	    // Mutant will generate its own death and division events
	    //if( _eventDeath != null ) s.RemoveEvent(_eventDeath);
	    //if( _eventMutate != null ) s.RemoveEvent(_eventMutate);
	    //if( _eventDivide != null ) s.RemoveEvent(_eventDivide);
	    s.addNewClone(this._cloneId, iRandLocus, false);
	    this._cloneId = s.getNewCloneId();
	    //double survival = s.getSurvival(this._cloneId);
	    //double reproduction = s.getReproduction(this._cloneId);
	    //double mutation = s.getMutation(this._cloneId);		
	    //setSurvival(survival);
	    //setReproduction(reproduction);
	    //setMutation(mutation);

	    // Get the reproduction, survival, neutral mutation and selective mutation rate class
	    // 1. Get the patterns
	    // 2. Extract rate class
	    int newDivisionClass = s.divisionRateToDivisionRateClassIndex.get(s.patternToDivisionRate.get(s.clonePhylogeny.getReproductionLociPattern(this._cloneId)));
	    int newDeathClass = s.deathRateToDeathRateClassIndex.get(s.patternToDeathRate.get(s.clonePhylogeny.getSurvivalLociPattern(this._cloneId)));
	    // get it once, since it is identical for both neutral and selective mut rate classes
	    String pat = s.clonePhylogeny.getReproductionMutationLociPattern(this._cloneId);
	    int newNeutralMutationClass = s.neutralMutationRateToNeutralMutationRateClassIndex.get(s.patternToNeutralMutationRate.get(pat));
	    int newSelectiveMutationClass = s.selectiveMutationRateToSelectiveMutationRateClassIndex.get(s.patternToSelectiveMutationRate.get(pat));

	    // If rate classes differ before and after mutation, change rate class of new crypt
	    if(newDivisionClass!=this.divisionClass){
		s.changeClassOfCryptFromDivisionClass(this.divisionClassIndex, this.divisionClass, newDivisionClass);
	    }
	    if(newDeathClass!=this.deathClass){
		s.changeClassOfCryptFromDeathClass(this.deathClassIndex, this.deathClass, newDeathClass);
	    }
	    if(newNeutralMutationClass!=this.neutralMutationClass){
		s.changeClassOfCryptFromNeutralMutationClass(this.neutralMutationClassIndex, this.neutralMutationClass, newNeutralMutationClass);
	    }
	    if(newSelectiveMutationClass!=this.selectiveMutationClass){
		s.changeClassOfCryptFromSelectiveMutationClass(this.selectiveMutationClassIndex, this.selectiveMutationClass, newSelectiveMutationClass);
	    }

	} else {
	    // nothin
	    //System.out.println("Clone "+this._cloneId+" received a double hit at locus !"+iRandLocus);
	}	   	    
    }




}
